<?php
/**
 ***********************************************************************************************************************
 * 规则处理类
 ***********************************************************************************************************************
 */
namespace soa\firewall\model;

/**
 * Class rule
 * @package soa\firewall\model
 */
class rule
{
    /**
     * 黑白名单规则键值前缀
     */
    const BLACKWHITE_PREFIX = 'firewall_access_rule_whiteblack_';

    /**
     * 频率规则键值前缀
     */
    const LIMIT_PREFIX = 'firewall_access_rule_limit_';

    /**
     * 有效期 永不过期
     */
    const EXPIRE = 0;

    /**
     * @var
     */
    private $service;

    /**
     * 必须传入$service才能使用
     * @param $service
     */
    public function __construct($service)
    {
        $this->service = $service;
    }

    /**
     * @param $service
     * @return mixed
     */
    public function setService($service)
    {
        $this->service = $service;
    }

    /**
     * @return mixed
     */
    public function getService()
    {
        return $this->service;
    }

    /**
     * 添加黑名单IP
     * @param $ip
     * @return bool
     */
    public function addBlack($ip)
    {
        if(empty($ip) || !is_string($ip)) {
            return false;
        }
        $key = md5(self::BLACKWHITE_PREFIX . $ip);
        $ret = $this->getRedisInstance()->set($key, 'black', self::EXPIRE);
        if(!empty($ret)) {
            $this->getDbInstance()->table('tbl_black_white')->delete(
                [
                    'ip' => $ip,
                    'is_segment' => '0',
                ]
            );
            $this->getDbInstance()->table('tbl_black_white')->insert(
                [
                    'ip' => $ip,
                    'is_black' => '1',
                    'is_segment' => '0',
                ]
            );
        }
        return $ret;
    }

    /**
     * @param $ip
     * @return bool|int
     */
    public function delBlack($ip)
    {
        if(empty($ip) || !is_string($ip)) {
            return false;
        }
        $key = md5(self::BLACKWHITE_PREFIX . $ip);
        $ret = $this->getRedisInstance()->del($key);
        if(!empty($ret)) {
            $this->getDbInstance()->table('tbl_black_white')->delete(
                [
                    'ip' => $ip
                ]
            );
        }
        return $ret;
    }

    /**
     * 添加黑名单IP段
     * @param $ip_segment
     * @return bool
     */
    public function addBlackSegment($ip_segment)
    {
        if(empty($ip_segment) || !is_string($ip_segment)) {
            return false;
        }
        $key = md5(self::BLACKWHITE_PREFIX . $ip_segment);
        $ret = $this->getRedisInstance()->set($key, 'black', self::EXPIRE);
        if(!empty($ret)) {
            $this->getDbInstance()->table('tbl_black_white')->delete(
                [
                    'ip' => $ip_segment,
                    'is_segment' => '1',
                ]
            );
            $this->getDbInstance()->table('tbl_black_white')->insert(
                [
                    'ip' => $ip_segment,
                    'is_black' => '1',
                    'is_segment' => '1',
                ]
            );
        }
        return $ret;
    }

    /**
     * @param $ip_segment
     * @return bool|int
     */
    public function delBlackSegment($ip_segment)
    {
        if(empty($ip_segment) || !is_string($ip_segment)) {
            return false;
        }
        $key = md5(self::BLACKWHITE_PREFIX . $ip_segment);

        $ret = $this->getRedisInstance()->del($key);
        if(!empty($ret)) {
            $this->getDbInstance()->table('tbl_black_white')->delete(
                [
                    'ip' => $ip_segment
                ]
            );
        }
        return $ret;
    }

    /**
     * 添加白名单IP
     * @param $ip
     * @return bool
     */
    public function addWhite($ip)
    {
        if(empty($ip) || !is_string($ip)) {
            return false;
        }
        $key = md5(self::BLACKWHITE_PREFIX . $ip);
        $ret = $this->getRedisInstance()->set($key, 'white', self::EXPIRE);
        if(!empty($ret)) {
            $this->getDbInstance()->table('tbl_black_white')->delete(
                [
                    'ip' => $ip,
                    'is_segment' => '0',
                ]
            );
            $this->getDbInstance()->table('tbl_black_white')->insert(
                [
                    'ip' => $ip,
                    'is_black' => '0',
                    'is_segment' => '0',
                ]
            );
        }
        return $ret;
    }

    /**
     * @param $ip
     * @return bool|int
     */
    public function delWhite($ip)
    {
        if(empty($ip) || !is_string($ip)) {
            return false;
        }
        $key = md5(self::BLACKWHITE_PREFIX . $ip);
        $ret = $this->getRedisInstance()->del($key);
        if(!empty($ret)) {
            $this->getDbInstance()->table('tbl_black_white')->insert(
                [
                    'ip' => $ip,
                ]
            );
        }
        return $ret;
    }

    /**
     * 添加白名单IP段
     * @param $ip_segment
     * @return bool
     */
    public function addWhiteSegment($ip_segment)
    {
        if(empty($ip_segment) || !is_string($ip_segment)) {
            return false;
        }
        $key = md5(self::BLACKWHITE_PREFIX . $ip_segment);
        $ret = $this->getRedisInstance()->set($key, 'white', self::EXPIRE);
        if(!empty($ret)) {
            $this->getDbInstance()->table('tbl_black_white')->delete(
                [
                    'ip' => $ip_segment,
                    'is_segment' => '1',
                ]
            );
            $this->getDbInstance()->table('tbl_black_white')->insert(
                [
                    'ip' => $ip_segment,
                    'is_black' => '0',
                    'is_segment' => '1',
                ]
            );
        }
        return $ret;
    }

    /**
     * @param $ip_segment
     * @return bool|int
     */
    public function delWhiteSegment($ip_segment)
    {
        if(empty($ip_segment) || !is_string($ip_segment)) {
            return false;
        }
        $key = md5(self::BLACKWHITE_PREFIX . $ip_segment);
        $ret = $this->getRedisInstance()->del($key);
        if(!empty($ret)) {
            $this->getDbInstance()->table('tbl_black_white')->delete(
                [
                    'ip' => $ip_segment,
                ]
            );
        }
        return $ret;
    }


    /**
     * @param $ip
     * @param $limit
     * @return bool
     */
    public function addIpLimitDaily($ip, $limit)
    {
        if(empty($ip) || !is_string($ip) || !is_numeric($limit)) {
            return false;
        }
        $key = md5(self::LIMIT_PREFIX . 'ip_' . $this->service . '_' . $ip);
        $ret = $this->getDbInstance()->table('tbl_rule')->getOne([
            'is_client' => '0',
            'service' => $this->service,
            'name' => $ip
        ]);
        if(!empty($ret)) {
            $ret['daily'] = $limit;
        } else {
            $ret = ['daily' => $limit];
        }
        $rs = $this->getRedisInstance()->set($key, $ret, self::EXPIRE);

        if(!empty($rs)) {
            $this->getDbInstance()->table('tbl_rule')->delete(
                [
                    'name' => $ip,
                    'type' => 'daily',
                    'is_client' => '0',
                    'service' => $this->service
                ]
            );
            $this->getDbInstance()->table('tbl_rule')->insert(
                [
                    'name' => $ip,
                    'cnt' => $limit,
                    'type' => 'daily',
                    'is_client' => '0',
                    'service' => $this->service
                ]
            );
        }
        return $ret;
    }

    /**
     * @param $ip
     * @return bool|int
     */
    public function delIpLimit($ip)
    {
        if(empty($ip) || !is_string($ip)) {
            return false;
        }
        $key = md5(self::LIMIT_PREFIX . 'ip_' . $this->service . '_' . $ip);
        $rs = $this->getRedisInstance()->del($key);
        if(!empty($rs)) {
            $this->getDbInstance()->table('tbl_rule')->delete(
                [
                    'name' => $ip,
                    'is_client' => '0',
                    'service' => $this->service
                ]
            );
        }
        return $rs;
    }

    /**
     * @param $ip
     * @param $limit
     * @return bool
     */
    public function addIpLimitHourly($ip, $limit)
    {
        if(empty($ip) || !is_string($ip) || !is_numeric($limit)) {
            return false;
        }
        $key = md5(self::LIMIT_PREFIX . 'ip_' . $this->service . '_' . $ip);
        $ret = $this->getDbInstance()->table('tbl_rule')->getOne([
            'is_client' => '0',
            'service' => $this->service,
            'name' => $ip
        ]);
        if(!empty($ret)) {
            $ret['hourly'] = $limit;
        } else {
            $ret = ['hourly' => $limit];
        }
        $rs = $this->getRedisInstance()->set($key, $ret, self::EXPIRE);
        if(!empty($rs)) {
            $this->getDbInstance()->table('tbl_rule')->delete(
                [
                    'name' => $ip,
                    'type' => 'hourly',
                    'is_client' => '0',
                    'service' => $this->service
                ]
            );
            $this->getDbInstance()->table('tbl_rule')->insert(
                [
                    'name' => $ip,
                    'cnt' => $limit,
                    'type' => 'hourly',
                    'is_client' => '0',
                    'service' => $this->service
                ]
            );
        }
        return $ret;
    }

    /**
     * @param $client
     * @param $limit
     * @return bool
     */
    public function addClientLimitDaily($client, $limit)
    {
        if(empty($client) || !is_string($client) || !is_numeric($limit)) {
            return false;
        }
        $key = md5(self::LIMIT_PREFIX . 'client_' . $this->service . '_' . $client);
        $ret = $this->getDbInstance()->table('tbl_rule')->getOne([
            'is_client' => '1',
            'service' => $this->service,
            'name' => $client
        ]);
        if(!empty($ret)) {
            $ret['daily'] = $limit;
        } else {
            $ret = ['daily' => $limit];
        }
        $rs = $this->getRedisInstance()->set($key, $ret, self::EXPIRE);
        if(!empty($rs)) {
            $this->getDbInstance()->table('tbl_rule')->delete(
                [
                    'name' => $client,
                    'type' => 'daily',
                    'is_client' => '1',
                    'service' => $this->service
                ]
            );
            $this->getDbInstance()->table('tbl_rule')->insert(
                [
                    'name' => $client,
                    'cnt' => $limit,
                    'type' => 'daily',
                    'is_client' => '1',
                    'service' => $this->service
                ]
            );
        }
        return $rs;
    }

    /**
     * @param $client
     * @param $limit
     * @return bool
     */
    public function addClientLimitHourly($client, $limit)
    {
        if(empty($client) || !is_string($client) || !is_numeric($limit)) {
            return false;
        }
        $key = md5(self::LIMIT_PREFIX . 'client_' . $this->service . '_' . $client);
        $ret = $this->getDbInstance()->table('tbl_rule')->getOne([
            'is_client' => '1',
            'service' => $this->service,
            'name' => $client
        ]);
        if(!empty($ret)) {
            $ret['hourly'] = $limit;
        } else {
            $ret = ['hourly' => $limit];
        }
        $rs = $this->getRedisInstance()->set($key, $ret, self::EXPIRE);
        if(!empty($rs)) {
            $this->getDbInstance()->table('tbl_rule')->delete(
                [
                    'name' => $client,
                    'type' => 'hourly',
                    'is_client' => '1',
                    'service' => $this->service
                ]
            );
            $this->getDbInstance()->table('tbl_rule')->insert(
                [
                    'name' => $client,
                    'cnt' => $limit,
                    'type' => 'hourly',
                    'is_client' => '1',
                    'service' => $this->service
                ]
            );
        }
        return $rs;
    }

    /**
     * @param $ip
     * @return bool|int
     */
    public function delClientLimit($client)
    {
        if(empty($client) || !is_string($client)) {
            return false;
        }
        $key = md5(self::LIMIT_PREFIX . 'ip_' . $this->service . '_' . $client);
        $rs = $this->getRedisInstance()->del($key);
        if(!empty($rs)) {
            $this->getDbInstance()->table('tbl_rule')->delete(
                [
                    'name' => $client,
                    'is_client' => '1',
                    'service' => $this->service
                ]
            );
        }
        return $rs;
    }

    /**
     * @param array $where
     * @param int $limit
     * @return mixed
     */
    public function getBlackWhitesFromDb(array $where = [], $limit = '')
    {
        return $this->getDbInstance()->table('tbl_black_white')->getAll($where, $limit, 'id desc');
    }

    /**
     * @param array $where
     * @param int $limit
     * @return mixed
     */
    public function getRulesFromDb(array $where = [], $limit = '')
    {
        return $this->getDbInstance()->table('tbl_rule')->getAll($where, $limit, 'id desc');
    }

    /**
     * @param $ip
     * @param string $client
     * @return array
     */
    public function getRules($ip, $client = '')
    {

        $segment = substr($ip, 0, strrpos($ip, '.'));
        $keys = [
            // 黑白名单
            md5(self::BLACKWHITE_PREFIX . $ip),
            md5(self::BLACKWHITE_PREFIX . $segment),
            // 频率IP
            md5(self::LIMIT_PREFIX . 'ip_' . $this->service . '_' . $ip),
            // 频率Client
            md5(self::LIMIT_PREFIX . 'client_' . $this->service . '_' . $client),
        ];
       // $start = microtime(true);
        $rs = [];
        foreach($keys as $key) {
            $rs[] = $this->getRedisInstance()->get($key);
        }
//myprint($rs);

        //$end = microtime(true) - $start;
        //myprint($end);
        return  [
            'blackwhite_ip' => $rs[0],
            'blackwhite_ip_segment' => $rs[1],
            'ip_limit' => !empty($rs[2]) ? $rs[2] : null,
            'client_limit' => !empty($rs[3]) ? $rs[3] : null,
        ];
    }

    /**
     * @var \artisan\cache\cacheDriver
     */
    private $redisInstance;

    /**
     * @return \artisan\cache\cacheDriver
     */
    private function getRedisInstance()
    {
        if(empty($this->redisInstance)) {
            $this->redisInstance = \artisan\cache::connect('firewall');
        }
        return $this->redisInstance;
    }

    /**
     * @var \artisan\db
     */
    private $dbInstance;

    /**
     * @return \artisan\db
     */
    private function getDbInstance()
    {
        if(empty($this->dbInstance)) {
            $this->dbInstance = \artisan\db::connect('firewall');
        }
        return $this->dbInstance;
    }


}